/*
   Copyright 2010 Bryce Alcock

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.com

 */
package com.ecocrypt.proxy.forwarder;

import com.ecocrypt.proxy.forwarder.gui.ForwardPanelTableModel;
import com.ecocrypt.proxy.forwarder.gui.ForwarderPanelTop;
import com.ecocrypt.proxy.forwarder.gui.ForwarderView;
import com.ecocrypt.proxy.forwarder.gui.ForwarderViewPanel;
import com.ecocrypt.proxy.forwarder.remote.ServiceRequestListener;
import java.io.Serializable;
import java.util.ArrayList;

/**
 * This is a very simple port forwarding proxy.  The proxy does the following:
 * <ol><li>Binds a local port to this process.</li>
 *     <li>Proxies all socket communication between the local port and a connected client to the remote machine/port.</li>
 *     <li>Records all communication data and timings.</li>
 *     <li>Buffers all Socket Reading to Maximize Network throughput.</li>
 * </ol>
 * @author Bryce.Alcock
 */
public class ForwardProxy {


    public static void main(String args[]){
	// If we have 2 or more parameters,
	// Then at least we have the correct number of paramters.
	if(args.length>1){
            for(int i =0;i<args.length;++i){
		//  -f lp:rm:rp is the format.
		//  Therfore, for each -f there must also be a String.
                if(args[i].equals("-f")){
                    ++i;
                    addForwarder(args[i]);
                }else if(args[i].equals("-rcPort")){

                    rcPort=Integer.parseInt(args[++i]);

                }else{
                    processCommand(args[i]);
                }
            }
            
	    startSocketListeners();

	    if(hasGui){
		ForwarderView.getInstance().setSize(800, 550);
		ForwarderView.getInstance().setVisible(true);
	    }
            if(allowRemoteConsole){
                serviceRequestListener = new ServiceRequestListener(rcPort);
                Thread t = new Thread(serviceRequestListener);
                t.start();
            }
        }else{
	    help();
	}
    }


    /**
     * This will start the forward threads, and thus start the local socket listeners.
     * Therefore, the local socket ports will become bound.
     * Any traffic to those ports will cause threads to start and attempts to 
     * connect to other machines.
     * <br><b>This CANNOT be called twice.</b> but it is private to control scope.
     */
    private static void startSocketListeners(){
        final String startThreadFormatString = "[Forwarder]{starting} %s - %s ";
	System.out.println("Attempting to start forwarders.  Forwarder Count ->" + forwarders.size() );

        for (Forwarder forwarder : forwarders) {
            //  Start the Socket Server.
	    Thread t = forwarder.getThread();
	    t.start();

            System.out.println(String.format(startThreadFormatString, t.getName(), forwarder.toString()));
        }
    }


    //-------------------------------------------------------------------------
    //
    //   Current Options.
    //         NO OPTIONS ARE WORKING CURRENTLY.
    //         EACH PORT PROXY WILL BE:
    //         STREAMING And IO.
    //
    //--------------------------------------------------------------------------
    private static boolean isNIO = false;
    private static boolean isBlocking = false;
    public  static boolean saveSendData =false;
    public  static boolean saveReceiveData = false;
    public static boolean closeClientOnEngineClose = true;
    public static boolean textOutput = true;
    public static boolean isProd = false;
    public static boolean allowRemoteConsole = true;
    public static boolean engineKeepAlive = true;
    public static boolean clientKeepAlive = true;
    public static int rcPort = 47906;

    static boolean hasGui = true;


    
    //  List of forwarders defined in the input command.
    private static ArrayList<Forwarder> forwarders = new ArrayList<Forwarder>();

    // This is public so that RemoteServiceRequest can grab the list easily.
    // It does not make sence to have a getter, because the getter would get a
    // reference to the ArrayList Object.  Better protection would be to make an
    // iterator, but then you could have threading issues.
    public static ArrayList<RemoteForwarderStub> remoteForwarderStubs = new ArrayList<RemoteForwarderStub>();

    //  The Service Request Listner is responsible for forwarding all
    //  messages on to the Remotely Attached Command consoles.
    private static ServiceRequestListener serviceRequestListener = null;
    
    public static void addServiceMessage(Serializable s){
        if(allowRemoteConsole){
            if(serviceRequestListener!=null)
            {
                serviceRequestListener.add(s);
            }
        }
    }
    /**
     * Create a new Forwarder, and add it to the list of forwarders.
     * No attempt to validate the inbound port, or outbound port is made.
     * 
     * @param s is the forwarder string, in the format localPort:remoteMachine:remotePort
     * @throws if the input string is not of the format int:String:int
     */
    public static void addForwarder(String s){
	//  A Runtime exception will occur in Forwarder if the
	//  String s is not int:String:int.
        Forwarder f = new Forwarder(s);
        forwarders.add(f);

	//  Set up the GUI if it is applicable.
	if(hasGui){
	    ForwardPanelTableModel tm = new ForwardPanelTableModel(f);
	    ForwarderPanelTop panel = new ForwarderPanelTop(tm,f);
	    ForwarderViewPanel.instance.addFowarderPanelTop(panel);
	}

        //  If we Allow Remote Console Connections, then we will need to keep 
        //  a list of all the forwarders that we have.
        if(allowRemoteConsole){
            remoteForwarderStubs.add(new RemoteForwarderStub(s));
        }
        f.getThread().start();
    }

    /**
     * Process the input command, and set the state of the proxy.
     * @param s
     * @throws IllegalArgumentException if the input command is not recognized.
     * @throws RuntimeExcpetion if a feature is not available.
     */
    private static void processCommand(String s){
        if(s.equals("-nio")){
            isNIO = true;
	    throw new RuntimeException("-nio option not yet implemented");
        }else if(s.equals("-io")){
            isNIO = false;
        }else if(s.equals("-block")){

            isBlocking=true;
	    throw new RuntimeException("-block option not yet implemented.");
        }else if(s.equals("-stream")){
            isBlocking = false;
        }else if(s.equals("-prod")){
            isProd = true;
        }else if(s.equals("-saveSendData")){
	    saveSendData=true;
        }else if(s.equals("-saveReceiveData")){
	    saveReceiveData=true;
        }else if(s.equals("-noEngineClose")){
	    closeClientOnEngineClose = false;
        }else if(s.equals("-noEngineKA")){
            engineKeepAlive = false;
        }else if(s.equals("-noClientKA")){
            clientKeepAlive = false;
        }else if(s.equals("-noRC")){
            allowRemoteConsole= false;
        }else if(s.equals("-noGui")){
	    hasGui = false;
        }else if(s.equals("-noTextOutput")){
	    textOutput = false;
        }else{
            help();
            System.out.println("Command: " +s +" IS NOT FOUND.  Try -help for Help.");
            throw new IllegalArgumentException("Unable to start because of the argument: " +s);
        }
    }

    public static void help(){

        StringBuilder sb = new StringBuilder();
        sb.append("USAGE: \n\t java -jar 47906Proxy.jar \n\t\t[-nio|-io] \n\t\t[-block|-stream]"
                +" \n\t\t[-saveSendData] \n\t\t[-saveReceiveData] "
                +"\n\t\t[-noEngineClose] \n\t\t[-noTextOutput] \n\t\t[-prod]  "
                +"\n\t\t-f lport:machine:rport [-f lport2:machine2:rport2 [-f ...]]");
        sb.append("\n\nSYNOPSIS:\n\t will create n socket servers listening on localhost:lport...localhost:lportN ");
        sb.append("\n\t Each piece of data sent to localhost:lport will be forwarded to machine:rport.");
        sb.append("\n\t Each read from localhost:lport will be pulled from machine:rport.");
        sb.append("\n\nMINIMUM COMMAND:\n\t java -jar netstat.jar -f 4000:apache.org:80");
        sb.append("\n\nOPTIONS:");
        sb.append("\n\t-prod        -  The logging output will only log Errors and nothing more no files will be created.");
        sb.append("\n\t-noGui       -  Start up the proxy without the Graphical user interface.");
        sb.append("\n\t-nio         -  {BROKEN-DISABLED}use Non-Blocking IO packages from Java.");
        sb.append("\n\t-io          -  The default, use standard IO from Java.");
        sb.append("\n\t-block       -  {BROKEN-DISABLED}Do Not Make any proxied bytes available until all are ready.\n\t\t (Fully Buffer Request and response in java).");
        sb.append("\n\t-stream      -  No buffering in Java. Byte for Byte Streaming.");
        sb.append("\n\t-noEngineClose   -  do Not close the client side when the server side closes.");
        sb.append("\n\t-noTextOutput    -  Store the Capture Data in Binary Format.");
        sb.append("\n\t-saveSendData    -  Save all the Data sent from the client in a string buffer for printing or post analysis.");
        sb.append("\n\t-saveReceiveData -  Save all the Data received from the Engine Server\n\t\t in to a string buffer for printing or post analysis.");
        sb.append("\n\nCAVEATS:\n\t Last directive wins, e.g.  java -jar -47906Proxy.jar -nio -io -nio -io -block -stream -stream -block -f 1025:dacar-db:4242");
        sb.append("\n\t\t will do standard io in a blocking mode binding localhost:1025 to the dacar-db on port 4242");
        System.out.println(sb.toString());

    }
}
